package org.hepeng.fabric.gateway.contract.spring;

import org.hepeng.fabric.contract.api.ChainCodeRegistry;
import org.hepeng.fabric.gateway.contract.proxy.FabricGatewayPoolContractProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.ClassUtils;

import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Set;

/**
 * @author he peng
 * @date 2022/3/26
 */

public class ClassPathContractScanner extends ClassPathBeanDefinitionScanner {

    private static final Logger LOG = LoggerFactory.getLogger(ClassPathContractScanner.class);

    private Class<? extends Annotation> annotationClass;

    public ClassPathContractScanner(BeanDefinitionRegistry registry) {
        super(registry, false);
    }

    @Override
    public Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

        if (beanDefinitions.isEmpty()) {
            LOG.warn("No Fabric Contract was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
        } else {
            try {
                processBeanDefinitions(beanDefinitions);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        }

        return beanDefinitions;
    }

    private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) throws ClassNotFoundException {

        FabricGatewayPoolContractProvider contractProvider = new FabricGatewayPoolContractProvider();

        GenericBeanDefinition bdf;
        for (BeanDefinitionHolder holder : beanDefinitions) {
            bdf = (GenericBeanDefinition) holder.getBeanDefinition();

            Class<?> contractAPIClass = ClassUtils.forName(bdf.getBeanClassName() , Thread.currentThread().getContextClassLoader());

            ChainCodeRegistry.add(contractAPIClass);

            bdf.getPropertyValues().add("contractInterface" , contractAPIClass);
            bdf.getPropertyValues().add("contractProvider" , contractProvider);

            bdf.setBeanClass(ContractFactoryBean.class);
            bdf.setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE);
            bdf.setAutowireCandidate(true);
            bdf.setLazyInit(false);

        }
    }

    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
    }

    public void setAnnotationClass(Class<? extends Annotation> annotationClass) {
        this.annotationClass = annotationClass;
    }


    public void registerFilters() {

        // if specified, use the given annotation and / or marker interface
        if (this.annotationClass != null) {
            addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
        }

        // exclude package-info.java
        addExcludeFilter((metadataReader, metadataReaderFactory) -> {
            String className = metadataReader.getClassMetadata().getClassName();
            return className.endsWith("package-info");
        });
    }
}
